﻿#region --- License & Copyright Notice ---
/*
ConsoleFx CommandLine Processing Library

Copyright (c) 2006-2012 Jeevan James
All rights reserved.

The contents of this file are made available under the terms of the
Eclipse Public License v1.0 (the "License") which accompanies this
distribution, and is available at the following URL:
http://opensource.org/licenses/eclipse-1.0.txt

Software distributed under the License is distributed on an "AS IS" basis,
WITHOUT WARRANTY OF ANY KIND, either expressed or implied. See the License for
the specific language governing rights and limitations under the License.

By using this software in any fashion, you are agreeing to be bound by the
terms of the License.
*/
#endregion

using System;
using System.Linq;

using ConsoleFx.Parsers;
using ConsoleFx.Resources;

namespace ConsoleFx.Programs.Simple
{
    /// <summary>
    /// Represents a simple console application that performs a discrete function and can optionally
    /// display help about its usage
    /// </summary>
    public sealed class ConsoleProgram : ConsoleProgramBase
    {
        private bool _showHelp;
        private readonly ExecuteHandler _normalHandler;

        /// <summary>
        /// Initializes a new instance of the ConsoleProgram class
        /// </summary>
        /// <param name="handler">A delegate that will execute the primary functionality of the application</param>
        /// <param name="includeHelpSupport">Adds support for providing help in the application</param>
        /// <param name="commandGrouping">The order in which the options and arguments can be specified on the command line</param>
        /// <param name="displayUsageOnError">Whether to automatically display help if an error occurs</param>
        public ConsoleProgram(ExecuteHandler handler, bool includeHelpSupport = false,
            CommandGrouping commandGrouping = CommandGrouping.DoesNotMatter, bool displayUsageOnError = true)
        {
            if (handler == null)
                throw new ArgumentNullException("handler");
            _normalHandler = handler;

            Properties.Available.Arguments.Add(ProgramContext.Normal, new ArgumentCollection());

            if (includeHelpSupport)
                IncludeHelpSupport();
            Properties.Behavior.Grouping = commandGrouping;
            Properties.Behavior.DisplayUsageOnError = displayUsageOnError;
        }

        public int Run()
        {
            try
            {
                string[] commandlineArgs = Environment.GetCommandLineArgs();
                Parse(commandlineArgs);

                if (string.IsNullOrEmpty(Properties.Context))
                    throw new ConsoleProgramException(ConsoleProgramException.Codes.ContextNotAvailable,
                        ConsoleProgramMessages.ContextNotAvailable);

                int exitCode = Properties.Context == ProgramContext.Help ? HelpHandler() : _normalHandler();
                return exitCode;
            }
            catch (Exception ex)
            {
                return HandleError(ex);
            }
        }

        public SimpleArgument AddArgument(bool optional = false)
        {
            var argument = new SimpleArgument {
                IsOptional = optional
            };
            Properties.Available.Arguments.Add(ProgramContext.Normal, argument);
            return argument;
        }

        public SimpleOption AddOption(string name, string shortName = null, OptionRequirement? requirement = null, int minOccurences = 0,
            int maxOccurences = 1, int? expectedOccurences = null, int minParameters = 0, int maxParameters = 0,
            int? expectedParameters = null, bool caseSensitive = false, int order = int.MaxValue)
        {
            var option = new SimpleOption(name) {
                CaseSensitive = caseSensitive,
                Order = order,
            };
            if (!string.IsNullOrWhiteSpace(shortName))
                option.ShortName = shortName;

            if (requirement.HasValue)
                option.NormalUsage.Requirement = requirement.Value;
            else if (expectedOccurences.HasValue)
                option.NormalUsage.ExpectedOccurences = expectedOccurences.Value;
            else
            {
                option.NormalUsage.MinOccurences = minOccurences;
                option.NormalUsage.MaxOccurences = maxOccurences;
            }

            if (expectedParameters.HasValue)
                option.NormalUsage.ExpectedParameters = expectedParameters.Value;
            else
            {
                option.NormalUsage.MinParameters = minParameters;
                option.NormalUsage.MaxParameters = maxParameters;
            }

            option.Usages[ProgramContext.Help].Requirement = OptionRequirement.NotAllowed;

            Properties.Available.Options.Add(option);
            return option;
        }

        protected override string GetContext()
        {
            //If no options and arguments are specified, then we need to decide whether the context
            //should be normal or help. We do this by checking if all the available options and arguments
            //are optional for the normal context, and if yes, then we return the normal context, otherwise
            //we return help.
            if (Properties.Specified.Options.Count == 0 && Properties.Specified.Arguments.Count == 0)
            {
                bool allOptionsOptionalForNormalContext = Properties.Available.Options.All(option => {
                    OptionUsage usage = option.Usages[ProgramContext.Normal];
                    return usage.Requirement == OptionRequirement.NotAllowed || usage.Requirement != OptionRequirement.Required;
                });

                ArgumentCollection arguments = Properties.Available.Arguments[ProgramContext.Normal];
                bool allArgumentsOptionalForNormalContext = arguments == null || arguments.All(arg => arg.IsOptional);

                return allOptionsOptionalForNormalContext && allArgumentsOptionalForNormalContext
                    ? ProgramContext.Normal : ProgramContext.Help;
            }

            //Otherwise, simply decide based on the value of the _showHelp field
            return _showHelp ? ProgramContext.Help : ProgramContext.Normal;
        }

        #region Help support
        private void IncludeHelpSupport()
        {
            var option = new SimpleOption(ConsoleProgramMessages.HelpOptionName) {
                ShortName = ConsoleProgramMessages.HelpOptionShortName,
                CaseSensitive = false,
                Order = 1,
                Handler = prms => _showHelp = true,
            };
            option.NormalUsage.Requirement = OptionRequirement.NotAllowed;
            option.Usages[ProgramContext.Help].Requirement = OptionRequirement.Optional;
            Properties.Available.Options.Add(option);
        }

        private int HelpHandler()
        {
            DisplayUsage();
            return 0;
        }
        #endregion
    }
}